package com.cjh.config;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.cjh.constant.JwtConstant;
import com.cjh.filter.JwtTokenCheckFilter;
import com.cjh.filter.MyAuthenticationFilter;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import com.cjh.entity.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Duration;
import java.util.*;

/**
 * web安全配置类
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Autowired
    private JwtTokenCheckFilter jwtTokenCheckFilter;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    @Qualifier("myUserDetailsService")
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.inMemoryAuthentication()
//                .withUser("cjh")
//                .password(passwordEncoder().encode("abc"))
//                .authorities("sys:add","sys:select");

        //使用自定义userDetailsService进行验证
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //启用token检查过滤器
        http.addFilterBefore(jwtTokenCheckFilter, UsernamePasswordAuthenticationFilter.class);
        //启用自定义登录验证过滤器
        http.addFilterAt(myAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        http.csrf().disable();
        http.formLogin()
                .loginProcessingUrl("/login")// 登录的接口
//                .successHandler(authenticationSuccessHandler())// 登录成功处理器
//                .failureHandler(authenticationFailureHandler())// 登录失败的处理器
                .permitAll();
        // 403异常处理器
        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
        // 禁用掉session 因为我们用的前后端分离的模式 根本不需要session 只要你有token (jwt) 就可以访问
        http.sessionManagement().disable();
        // 放行所有的接口 因为我们使用了jwt 只要请求携带了jwt  只要能被我解析 那么就的相当于登录成功了
        http.authorizeRequests()
                .antMatchers("/**")
                .permitAll();
    }

    /**
     * 注入自定义登录验证过滤器（获取json格式的用户名、密码）
     * @return
     * @throws Exception
     */
    @Bean
    public MyAuthenticationFilter myAuthenticationFilter() throws Exception {
        MyAuthenticationFilter filter = new MyAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
            /**
             * 登录成功返回值
             * 1.拿到用户信息
             * 2.根据用户信息生成token
             * 3.写出去
             * @param request
             * @param response
             * @param authentication
             * @throws IOException
             * @throws ServletException
             */
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                response.setContentType("application/json;charset=utf-8");
                // 获取当前用户信息了 强转
                User principal = (User) authentication.getPrincipal();
                // 拿到我们的用户名
                String userName = principal.getUsername();
                // 拿权限
                Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
                List<String> authStrs = new ArrayList<>(authorities.size() * 2);
                // 设置权限
                authorities.forEach(auth -> authStrs.add(auth.getAuthority()));
                // 生成jwt
                // 颁发时间
                Date createTime = new Date();
                // 过期时间
                Calendar now = Calendar.getInstance();
                // 设置未来的时间
                now.set(Calendar.SECOND, JwtConstant.EXPIRE_TIME);
                Date expireTime = now.getTime();
                // header
                HashMap<String, Object> header = new HashMap<>(4);
                header.put("alg", "HS256");
                header.put("typ", "JWT");
                // 载体
                String jwt = JWT.create()
                        .withHeader(header)
                        .withIssuedAt(createTime)
                        .withExpiresAt(expireTime)
                        .withClaim("userName", userName) // 设置用户名
                        .withClaim("auths", authStrs) // 设置权限
                        .sign(Algorithm.HMAC256(JwtConstant.SIGN));
                // 为了解决登出 我们就需要保存token 我们 用redis保存 这里不是共享session 让服务端和客户端建立有状态
                redisTemplate.opsForValue().set(JwtConstant.JWT_PREFIX + jwt, "", Duration.ofSeconds(JwtConstant.EXPIRE_TIME));
                // 写出去
                HashMap<String, Object> map = new HashMap<>(8);
                map.put("access_token", jwt);
                map.put("expire_time", JwtConstant.EXPIRE_TIME);
                map.put("type", "bearer");
                ObjectMapper objectMapper = new ObjectMapper();
                String s = objectMapper.writeValueAsString(map);
                PrintWriter writer = response.getWriter();
                writer.write(s);
                writer.flush();
                writer.close();
            }
        });
        filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
            /**
             * 登录失败的返回值
             * @param request
             * @param response
             * @param e
             * @throws IOException
             * @throws ServletException
             */
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
                response.setContentType("application/json;charset=utf-8");
                PrintWriter out = response.getWriter();
                Map<String, Object> map = new HashMap<>();
                map.put("code", 401);
                if (e instanceof LockedException) {
                    map.put("msg", "账户被锁定，登录失败!");
                } else if (e instanceof BadCredentialsException) {
                    map.put("msg", "用户名或密码输入错误，登录失败!");
                } else if (e instanceof DisabledException) {
                    map.put("msg", "账户被禁用，登录失败!");
                } else if (e instanceof AccountExpiredException) {
                    map.put("msg", "账户过期，登录失败!");
                } else if (e instanceof CredentialsExpiredException) {
                    map.put("msg", "密码过期，登录失败!");
                } else {
                    map.put("msg", "登录失败!");
                }
                out.write(new ObjectMapper().writeValueAsString(map));
                out.flush();
                out.close();
            }
        });
        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }

    /**
     * 自定义请求403的处理器
     * 返回json
     * @return
     */
    @Bean
    public AccessDeniedHandler accessDeniedHandler(){
        return (request, response, exception)->{
            response.setContentType("application/json;charset=utf-8");
            Map<String,Object> map=new HashMap<>();
            map.put("code",403);
            map.put("msg","您没有权限");
            ObjectMapper objectMapper=new ObjectMapper();
            String s = objectMapper.writeValueAsString(map);
            PrintWriter writer = response.getWriter();
            //把返回信息写出去返回给前端
            writer.write(s);
            writer.flush();
            writer.close();
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }



//    /**
//     * 登录成功返回值
//     * 1.拿到用户信息
//     * 2.根据用户信息生成token
//     * 3.写出去
//     *
//     * @return
//     */
//    @Bean
//    public AuthenticationSuccessHandler authenticationSuccessHandler() {
//        return (request, response, authentication) -> {
//            response.setContentType("application/json;charset=utf-8");
//            // 获取当前用户信息了 强转
//            User principal = (User) authentication.getPrincipal();
//            // 拿到我们的用户名
//            String userName = principal.getUsername();
//            // 拿权限
//            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
//            List<String> authStrs = new ArrayList<>(authorities.size() * 2);
//            // 设置权限
//            authorities.forEach(auth -> authStrs.add(auth.getAuthority()));
//            // 生成jwt
//            // 颁发时间
//            Date createTime = new Date();
//            // 过期时间
//            Calendar now = Calendar.getInstance();
//            // 设置未来的时间
//            now.set(Calendar.SECOND, JwtConstant.EXPIRE_TIME);
//            Date expireTime = now.getTime();
//            // header
//            HashMap<String, Object> header = new HashMap<>(4);
//            header.put("alg", "HS256");
//            header.put("typ", "JWT");
//            // 载体
//            String jwt = JWT.create()
//                    .withHeader(header)
//                    .withIssuedAt(createTime)
//                    .withExpiresAt(expireTime)
//                    .withClaim("userName", userName) // 设置用户名
//                    .withClaim("auths", authStrs) // 设置权限
//                    .sign(Algorithm.HMAC256(JwtConstant.SIGN));
//            // 为了解决登出 我们就需要保存token 我们 用redis保存 这里不是共享session 让服务端和客户端建立有状态
//            redisTemplate.opsForValue().set(JwtConstant.JWT_PREFIX + jwt, "", Duration.ofSeconds(JwtConstant.EXPIRE_TIME));
//            // 写出去
//            HashMap<String, Object> map = new HashMap<>(8);
//            map.put("access_token", jwt);
//            map.put("expire_time", JwtConstant.EXPIRE_TIME);
//            map.put("type", "bearer");
//            ObjectMapper objectMapper = new ObjectMapper();
//            String s = objectMapper.writeValueAsString(map);
//            PrintWriter writer = response.getWriter();
//            writer.write(s);
//            writer.flush();
//            writer.close();
//        };
//    }
//
//    /**
//     * 登录失败的json
//     *
//     * @return
//     */
//    @Bean
//    public AuthenticationFailureHandler authenticationFailureHandler() {
//        return (request, response, exception) -> {
//            response.setContentType("application/json;charset=utf-8");
//            Map<String,Object> map=new HashMap<>();
//            map.put("code",401);
//            //instanceof判断左边是否等于右边的一个实例
//            if (exception instanceof LockedException){
//                map.put("msg","账户被锁定");
//            }else if (exception instanceof BadCredentialsException){
//                map.put("msg","用户名或密码错误");
//            }else if (exception instanceof DisabledException){
//                map.put("msg","账户被禁用");
//            }else if (exception instanceof AccountExpiredException){
//                map.put("msg","账户已过期");
//            } else {
//                map.put("msg","登录失败");
//            }
//            ObjectMapper objectMapper=new ObjectMapper();
//            String s = objectMapper.writeValueAsString(map);
//            PrintWriter writer = response.getWriter();
//            //把返回信息写出去返回给前端
//            writer.write(s);
//            writer.flush();
//            writer.close();
//        };
//    }
}
